# 开发者：_Iamsleepingnow
# 开发时间：2024-02-21 13:37
# 开发功能：Noita存档备份程序2.0
# encoding = utf-8
# -----------------------------------------
# 脚本介绍：
# 此脚本用于实现半自动Noita存读档，通过复制替换原存档save00来完成（save00是游戏的当前存档路径）
# 在使用此脚本前，请务必要先将存档进行手动备份，潜在的程序bug或误操作造成的丢档的损失作者将不予承担
# ---
# 在使用脚本之前，需要事先在游戏中关闭steam云存档，否则脚本不会正常工作
# ---
# 如何使用：
# 1. 在需要保存游戏进度的时候，点游戏内部的“保存与退出”来手动保存游戏，待游戏正常退出后进入下一步：
# 2. 点击脚本中的“新建并备份存档”按钮来新建存档，稍等片刻后就会在页面中看到新建的存档，这时可以通过“打开总存档目录”来查看存档情况
#    每一个存档都可以在脚本中设置“名称”、“备注”和“提示图片”，
#    “路径”字符串是自动生成的，命名规则以“save00_SL_”开头，以8位随机码结尾，切勿更改此名称
#    脚本信息保存在该存档目录中的“SL_Messages.json”文件里，此文件切勿更改或删除
# 3. 点击单个存档中的“备份存档”按钮时，会将“save00”复制一份并覆盖该存档
#    点击“替换存档”按钮时，会将该存档复制一份来替换“save00”存档
#    点击“删除存档”按钮时，会将该存档删除，不会影响“save00”存档
# 4. 每当按下任意存档的“备份存档”按钮时，除了更新该存档的信息外，还会在存档目录下生成一个叫“Safety”的备份存档
#    此备份存档不会被删除，用于保证游戏存档不会被永久删除
# 5. 存档页面中有一个长着像骰子的按钮，这个是用来切换显示图片的，图片路径是本脚本同级目录下的“Images”文件夹
#    这个文件夹的名称和路径不可修改，所以推荐给本脚本新建一个快捷方式来使用
#    “./Images/”里的图片可以自行替换
# 6. 当想要回档（或者叫返回游戏进度）时，先保证游戏是关闭的，再点击某个存档的“替换存档”，然后在游戏主菜单里“继续”即可
#    注意，这个“替换存档”可能会由于存档大小的增大而变得卡顿，当卡顿时，请勿进行其他操作

import json
import random
import sys, os, shutil
from os import startfile
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5 import QtCore, QtGui, QtWidgets

path_program = os.getenv("LOCALAPPDATA") + "Low" + "\\Nolla_Games_Noita" # 存档环境路径
path_save00 = path_program + "\\save00" # 存档路径
path_images = '.\\Images\\' # 图片路径
path_save00_SL_Safety = path_save00 + "_SL_Safety" # 存档备份路径_安全模式（保证游戏存档不会被永久删除）
save_list = []

# todo 存档单位
class WidgetElement(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()
    def initUi(self):
        self.setMinimumSize(QtCore.QSize(500, 270))
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self)
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.WG_SaveFileGroup = QtWidgets.QFrame(self)
        self.WG_SaveFileGroup.setMinimumSize(QtCore.QSize(0, 250))
        self.WG_SaveFileGroup.setMaximumSize(QtCore.QSize(16777215, 250))
        self.WG_SaveFileGroup.setStyleSheet("color: rgb(255, 255, 255); background-color: rgb(135, 119, 174);")
        self.WG_SaveFileGroup.setFrameShape(QtWidgets.QFrame.Box)
        self.WG_SaveFileGroup.setFrameShadow(QtWidgets.QFrame.Plain)
        self.WG_SaveFileGroup.setObjectName("WG_SaveFileGroup")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.WG_SaveFileGroup)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.WG_Name = QtWidgets.QWidget(self.WG_SaveFileGroup)
        self.WG_Name.setObjectName("WG_Name")
        self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.WG_Name)
        self.horizontalLayout_5.setContentsMargins(2, 2, 2, 2)
        self.horizontalLayout_5.setObjectName("horizontalLayout_5")
        self.LB_NameTitle = QtWidgets.QLabel(self.WG_Name)
        self.LB_NameTitle.setObjectName("LB_NameTitle")
        self.horizontalLayout_5.addWidget(self.LB_NameTitle)
        self.LE_Name = QtWidgets.QLineEdit(self.WG_Name)
        font = QtGui.QFont()
        font.setPointSize(16)
        self.LE_Name.setFont(font)
        self.LE_Name.setStyleSheet("color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);")
        self.LE_Name.setObjectName("LE_Name")
        self.horizontalLayout_5.addWidget(self.LE_Name)
        self.PB_SortUp = QtWidgets.QPushButton(self.WG_Name)
        self.PB_SortUp.setMaximumSize(QtCore.QSize(40, 16777215))
        self.PB_SortUp.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_SortUp.setObjectName("PB_SortUp")
        self.horizontalLayout_5.addWidget(self.PB_SortUp)
        self.PB_SortDown = QtWidgets.QPushButton(self.WG_Name)
        self.PB_SortDown.setMaximumSize(QtCore.QSize(40, 16777215))
        self.PB_SortDown.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_SortDown.setObjectName("PB_SortDown")
        self.horizontalLayout_5.addWidget(self.PB_SortDown)
        self.verticalLayout.addWidget(self.WG_Name)
        self.WG_Suffix = QtWidgets.QWidget(self.WG_SaveFileGroup)
        self.WG_Suffix.setObjectName("WG_Suffix")
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.WG_Suffix)
        self.horizontalLayout_4.setContentsMargins(2, 2, 2, 2)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.LB_PathTitle = QtWidgets.QLabel(self.WG_Suffix)
        self.LB_PathTitle.setObjectName("LB_SuffixTitle")
        self.horizontalLayout_4.addWidget(self.LB_PathTitle)
        self.LB_Path = QtWidgets.QLabel(self.WG_Suffix)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.LB_Path.sizePolicy().hasHeightForWidth())
        self.LB_Path.setSizePolicy(sizePolicy)
        self.LB_Path.setObjectName("LB_Suffix")
        self.horizontalLayout_4.addWidget(self.LB_Path)
        self.verticalLayout.addWidget(self.WG_Suffix)
        self.WG_Function = QtWidgets.QWidget(self.WG_SaveFileGroup)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.WG_Function.sizePolicy().hasHeightForWidth())
        self.WG_Function.setSizePolicy(sizePolicy)
        self.WG_Function.setObjectName("WG_Function")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.WG_Function)
        self.horizontalLayout_3.setContentsMargins(2, 2, 2, 2)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.LB_FunctionTitle = QtWidgets.QLabel(self.WG_Function)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.LB_FunctionTitle.sizePolicy().hasHeightForWidth())
        self.LB_FunctionTitle.setSizePolicy(sizePolicy)
        self.LB_FunctionTitle.setObjectName("LB_FunctionTitle")
        self.horizontalLayout_3.addWidget(self.LB_FunctionTitle)
        self.LO_Function = QtWidgets.QFrame(self.WG_Function)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.LO_Function.sizePolicy().hasHeightForWidth())
        self.LO_Function.setSizePolicy(sizePolicy)
        self.LO_Function.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.LO_Function.setFrameShadow(QtWidgets.QFrame.Raised)
        self.LO_Function.setObjectName("LO_Function")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.LO_Function)
        self.horizontalLayout.setContentsMargins(0, 6, 0, 6)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.PB_Save = QtWidgets.QPushButton(self.LO_Function)
        self.PB_Save.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_Save.setStyleSheet("background-color: rgb(207, 191, 111); color: rgb(0, 0, 0);")
        self.PB_Save.setObjectName("PB_Save")
        self.horizontalLayout.addWidget(self.PB_Save)
        self.PB_Replace = QtWidgets.QPushButton(self.LO_Function)
        self.PB_Replace.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_Replace.setStyleSheet("background-color: rgb(207, 191, 111); color: rgb(0, 0, 0);")
        self.PB_Replace.setObjectName("PB_Replace")
        self.horizontalLayout.addWidget(self.PB_Replace)
        self.PB_Clear = QtWidgets.QPushButton(self.LO_Function)
        self.PB_Clear.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_Clear.setStyleSheet("background-color: rgb(207, 191, 111); color: rgb(0, 0, 0);")
        self.PB_Clear.setObjectName("PB_Clear")
        self.horizontalLayout.addWidget(self.PB_Clear)
        self.PB_Open = QtWidgets.QPushButton(self.LO_Function)
        self.PB_Open.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_Open.setObjectName("PB_Open")
        self.horizontalLayout.addWidget(self.PB_Open)
        self.PB_RandomImage = QtWidgets.QPushButton(self.LO_Function)
        self.PB_RandomImage.setMaximumSize(QtCore.QSize(40, 16777215))
        self.PB_RandomImage.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.PB_RandomImage.setObjectName("PB_RandomImage")
        self.horizontalLayout.addWidget(self.PB_RandomImage)
        self.horizontalLayout_3.addWidget(self.LO_Function)
        self.verticalLayout.addWidget(self.WG_Function)
        self.WG_Note = QtWidgets.QWidget(self.WG_SaveFileGroup)
        self.WG_Note.setObjectName("WG_Note")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.WG_Note)
        self.horizontalLayout_2.setContentsMargins(2, 2, 2, 2)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.LB_NoteTitle = QtWidgets.QLabel(self.WG_Note)
        self.LB_NoteTitle.setObjectName("LB_NoteTitle")
        self.horizontalLayout_2.addWidget(self.LB_NoteTitle)
        self.PTE_Note = QtWidgets.QPlainTextEdit(self.WG_Note)
        self.PTE_Note.setMaximumSize(QtCore.QSize(16777215, 500))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.PTE_Note.setFont(font)
        self.PTE_Note.setStyleSheet("color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);")
        self.PTE_Note.setTabStopWidth(80)
        self.PTE_Note.setObjectName("PTE_Note")
        self.horizontalLayout_2.addWidget(self.PTE_Note)
        self.verticalLayout.addWidget(self.WG_Note)
        self.horizontalLayout_6.addWidget(self.WG_SaveFileGroup)
        self.LB_ImageDisplay = QtWidgets.QLabel(self)
        self.LB_ImageDisplay.setMinimumSize(QtCore.QSize(250, 250))
        self.LB_ImageDisplay.setMaximumSize(QtCore.QSize(250, 250))
        font = QtGui.QFont()
        font.setPointSize(25)
        self.LB_ImageDisplay.setFont(font)
        self.LB_ImageDisplay.setStyleSheet("background-color: rgb(135, 119, 174);")
        self.LB_ImageDisplay.setFrameShape(QtWidgets.QFrame.Box)
        self.LB_ImageDisplay.setAlignment(QtCore.Qt.AlignCenter)
        self.LB_ImageDisplay.setObjectName("LB_ImageDisplay")
        self.horizontalLayout_6.addWidget(self.LB_ImageDisplay)
        self.horizontalLayout_6.setSpacing(0)
        self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
        self.retranslateUi(self)
        QtCore.QMetaObject.connectSlotsByName(self)
        self.register_event()
    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "ELEMENT"))
        self.LB_NameTitle.setText(_translate("Form", "名称"))
        self.LE_Name.setPlaceholderText(_translate("Form", ">_新存档"))
        self.PB_SortUp.setText(_translate("Form", "👆"))
        self.PB_SortDown.setText(_translate("Form", "👇"))
        self.LB_PathTitle.setText(_translate("Form", "路径"))
        self.LB_Path.setText(_translate("Form", "save00_XXXXXXXX"))
        self.LB_FunctionTitle.setText(_translate("Form", "操作"))
        self.PB_Save.setText(_translate("Form", "备份存档"))
        self.PB_Replace.setText(_translate("Form", "替换存档"))
        self.PB_Clear.setText(_translate("Form", "删除存档"))
        self.PB_Open.setText(_translate("Form", "打开路径"))
        self.PB_RandomImage.setText(_translate("Form", "🎲"))
        self.LB_NoteTitle.setText(_translate("Form", "备注"))
        self.PTE_Note.setPlaceholderText(_translate("Form", ">_"))
        self.LB_ImageDisplay.setText(_translate("Form", "IMAGE"))
        #
        self.LB_NameTitle.setFont(QFont('等线', 12))
        self.LB_PathTitle.setFont(QFont('等线', 12))
        self.LB_FunctionTitle.setFont(QFont('等线', 12))
        self.LB_NoteTitle.setFont(QFont('等线', 12))
        self.LE_Name.setFont(QFont('等线', 12))
        self.LB_Path.setFont(QFont('等线', 12))
        self.PB_SortUp.setFont(QFont('等线', 12))
        self.PB_SortDown.setFont(QFont('等线', 12))
        self.PB_Save.setFont(QFont('等线', 12))
        self.PB_Replace.setFont(QFont('等线', 12))
        self.PB_Clear.setFont(QFont('等线', 12))
        self.PB_Open.setFont(QFont('等线', 12))
        self.PB_RandomImage.setFont(QFont('等线', 12))
        self.PTE_Note.setFont(QFont('等线', 12))
        self.LB_ImageDisplay.setFont(QFont('等线', 12))

    # 注册事件
    def register_event(self):
        self.PB_Save.clicked.connect(self.event_FileSave)
        self.PB_Replace.clicked.connect(self.event_FileLoad)
        self.PB_Clear.clicked.connect(self.event_FileClear)
        self.PB_Open.clicked.connect(self.event_OpenPath)
        self.LE_Name.textChanged.connect(self.event_OnNameChange)
        self.PTE_Note.textChanged.connect(self.event_OnNoteChange)
        self.PB_SortUp.clicked.connect(self.event_OnSortUpChange)
        self.PB_SortDown.clicked.connect(self.event_OnSortDownChange)
        self.PB_RandomImage.clicked.connect(self.event_OnRandomImage)
    # 设置界面显示
    def set_display(self, element):
        self.LE_Name.setText(element['NAME'])
        self.LB_Path.setText(element['PATH'])
        self.PTE_Note.setPlainText(element['NOTE'])
        self.LB_ImageDisplay.setText('没有找到图片') # element['PATH']
        pass

    # 随机图片
    def random_image(self):
        # 在path_images里获取随机一张图片路径
        image_list = os.listdir(path_images)
        image_path = path_images + '\\' + image_list[random.randint(0, len(image_list) - 1)]
        return image_path

    # 备份存档
    def event_FileSave(self):
        filename = self.LB_Path.text()
        if os.path.exists(path_program):  # 存档路径是否存在
            # 如果存档环境路径中存在save00_SL，则将其删除
            if os.path.exists(path_save00 + filename):
                shutil.rmtree(path_save00 + filename)
            if os.path.exists(path_save00_SL_Safety):
                shutil.rmtree(path_save00_SL_Safety)
            # 将path_save00文件夹在原路径备份一份，并更名为save00_SL
            shutil.copytree(path_save00, path_save00 + filename)
            shutil.copytree(path_save00, path_save00_SL_Safety)
    # 替换存档
    def event_FileLoad(self):
        filename = self.LB_Path.text()
        if os.path.exists(path_program):  # 存档路径是否存在
            # 如果存档环境路径中存在save00_SL，则删除save00，复制save00_SL并重命名为save00
            if os.path.exists(path_save00 + filename):
                shutil.rmtree(path_save00)
                shutil.copytree(path_save00 + filename, path_save00)
                # 删除save00里面的SL_Messages.json文件
                for root, dirs, files in os.walk(path_save00):
                    for file in files:
                        if file == 'SL_Messages.json':
                            os.remove(root + '\\' + file)
    # 删除存档
    def event_FileClear(self):
        filename = self.LB_Path.text()
        if os.path.exists(path_program):  # 存档路径是否存在
            # 如果存档环境路径中存在save00_SL，则将其删除
            if os.path.exists(path_save00 + filename):
                shutil.rmtree(path_save00 + filename)
        # 删除数组中的元素
        for ele in save_list:
            if ele['PATH'] == filename:
                save_list.remove(ele)
                break
    # 打开存档
    def event_OpenPath(self):
        filename = self.LB_Path.text()
        # 遍历path_program中的所有目录，寻找所有名称为SL_Messages.json的文件并读取其中的内容
        for root, dirs, files in os.walk(path_program):
            for file in files:
                if file == 'SL_Messages.json':
                    json_read = json.load(open(root + '\\' + file, 'r'))
                    if json_read['PATH'] == filename:
                        startfile(root)
        # if os.path.exists(path_program + '\\save00' + filename):  # 存档路径是否存在
        #     startfile(path_program)  # 打开文件夹窗口

    # 当存档名被修改
    def event_OnNameChange(self):
        # 在save_list中找到PATH一致的元素，将NAME修改
        for ele in save_list:
            if ele['PATH'] == self.LB_Path.text():
                ele['NAME'] = self.LE_Name.text()
                break
    # 当备注被修改
    def event_OnNoteChange(self):
        # 在save_list中找到PATH一致的元素，将NOTE修改
        for ele in save_list:
            if ele['PATH'] == self.LB_Path.text():
                ele['NOTE'] = self.PTE_Note.toPlainText()
                break

    # 当向上排序时
    def event_OnSortUpChange(self):
        # 在save_list中找到PATH一致的元素，并与前一个元素交换
        for i in range(len(save_list)):
            if save_list[i]['PATH'] == self.LB_Path.text():
                if i > 0:
                    save_list[i], save_list[i - 1] = save_list[i - 1], save_list[i]
                    break
    # 当向下排序时
    def event_OnSortDownChange(self):
        # 在save_list中找到PATH一致的元素，并与后一个元素交换
        for i in range(len(save_list)):
            if save_list[i]['PATH'] == self.LB_Path.text():
                if i < len(save_list) - 1:
                    save_list[i], save_list[i + 1] = save_list[i + 1], save_list[i]
                    break
    # 当随机图片时
    def event_OnRandomImage(self):
        self.LB_ImageDisplay.clear()
        image_path = self.random_image()
        # 在save_list中找到PATH一致的元素，将IMAGE修改
        for ele in save_list:
            if ele['PATH'] == self.LB_Path.text():
                ele['IMAGE'] = image_path
                break
        if image_path != '':
            pixmap = QPixmap(image_path)
            proporation = pixmap.height() / pixmap.width()
            pixmap.setDevicePixelRatio(proporation)
            w, h = self.LB_ImageDisplay.width(), self.LB_ImageDisplay.height()
            pixmap = pixmap.scaled(int(w / 3 * 2), int(h / 3 * 2))
            self.LB_ImageDisplay.setPixmap(pixmap)
        else:
            self.LB_ImageDisplay.setText('没有找到图片')

# todo 主窗口
class MainWindow(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(960, 750)
        Form.setMinimumSize(QtCore.QSize(960, 512))
        Form.setStyleSheet("background-color: rgb(135, 119, 174);")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(Form)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.WG_Title = QtWidgets.QFrame(Form)
        self.WG_Title.setStyleSheet("background-color: rgb(207, 191, 111)")
        self.WG_Title.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.WG_Title.setFrameShadow(QtWidgets.QFrame.Raised)
        self.WG_Title.setObjectName("WG_Title")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.WG_Title)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.LB_Title = QtWidgets.QLabel(self.WG_Title)
        font = QtGui.QFont()
        font.setFamily("等线")
        font.setPointSize(16)
        self.LB_Title.setFont(font)
        self.LB_Title.setStyleSheet("")
        self.LB_Title.setAlignment(QtCore.Qt.AlignCenter)
        self.LB_Title.setObjectName("LB_Title")
        self.horizontalLayout.addWidget(self.LB_Title)
        self.verticalLayout_2.addWidget(self.WG_Title)
        self.WG_Use = QtWidgets.QFrame(Form)
        self.WG_Use.setFrameShape(QtWidgets.QFrame.Box)
        self.WG_Use.setFrameShadow(QtWidgets.QFrame.Plain)
        self.WG_Use.setLineWidth(1)
        self.WG_Use.setObjectName("WG_Use")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.WG_Use)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.WG_UseFunction = QtWidgets.QFrame(self.WG_Use)
        self.WG_UseFunction.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.WG_UseFunction.setFrameShadow(QtWidgets.QFrame.Raised)
        self.WG_UseFunction.setObjectName("WG_UseFunction")
        self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.WG_UseFunction)
        self.verticalLayout_5.setObjectName("verticalLayout_5")
        self.PB_AddSave = QtWidgets.QPushButton(self.WG_UseFunction)
        self.PB_AddSave.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.PB_AddSave.sizePolicy().hasHeightForWidth())
        self.PB_AddSave.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setFamily("等线")
        font.setPointSize(11)
        self.PB_AddSave.setFont(font)
        self.PB_AddSave.setStyleSheet("background-color: rgb(207, 191, 111)")
        self.PB_AddSave.setObjectName("PB_AddSave")
        self.verticalLayout_5.addWidget(self.PB_AddSave)
        self.PB_OpenMain = QtWidgets.QPushButton(self.WG_UseFunction)
        self.PB_OpenMain.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.PB_OpenMain.sizePolicy().hasHeightForWidth())
        self.PB_OpenMain.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setFamily("等线")
        font.setPointSize(11)
        self.PB_OpenMain.setFont(font)
        self.PB_OpenMain.setStyleSheet("background-color: rgb(207, 191, 111)")
        self.PB_OpenMain.setObjectName("PB_OpenMain")
        self.verticalLayout_5.addWidget(self.PB_OpenMain)
        self.horizontalLayout_2.addWidget(self.WG_UseFunction)
        self.GB_UseMethod = QtWidgets.QGroupBox(self.WG_Use)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.GB_UseMethod.setFont(font)
        self.GB_UseMethod.setStyleSheet("")
        self.GB_UseMethod.setFlat(True)
        self.GB_UseMethod.setCheckable(False)
        self.GB_UseMethod.setObjectName("GB_UseMethod")
        self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.GB_UseMethod)
        self.verticalLayout_4.setSpacing(0)
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        #
        self.LB_UseMethod1 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod1.setFont(font)
        self.LB_UseMethod1.setObjectName("LB_UseMethod1")
        self.verticalLayout_4.addWidget(self.LB_UseMethod1)
        #
        self.LB_UseMethod2 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod2.setFont(font)
        self.LB_UseMethod2.setObjectName("LB_UseMethod2")
        self.verticalLayout_4.addWidget(self.LB_UseMethod2)
        #
        self.LB_UseMethod3 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod3.setFont(font)
        self.LB_UseMethod3.setObjectName("LB_UseMethod3")
        self.verticalLayout_4.addWidget(self.LB_UseMethod3)
        #
        self.LB_UseMethod4 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod4.setFont(font)
        self.LB_UseMethod4.setObjectName("LB_UseMethod4")
        self.verticalLayout_4.addWidget(self.LB_UseMethod4)
        #
        self.LB_UseMethod5 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod5.setFont(font)
        self.LB_UseMethod5.setObjectName("LB_UseMethod5")
        self.verticalLayout_4.addWidget(self.LB_UseMethod5)
        #
        self.LB_UseMethod6 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod6.setFont(font)
        self.LB_UseMethod6.setObjectName("LB_UseMethod6")
        self.verticalLayout_4.addWidget(self.LB_UseMethod6)
        #
        self.LB_UseMethod7 = QtWidgets.QLabel(self.GB_UseMethod)
        font = QtGui.QFont()
        font.setFamily("等线")
        self.LB_UseMethod7.setFont(font)
        self.LB_UseMethod7.setObjectName("LB_UseMethod7")
        self.verticalLayout_4.addWidget(self.LB_UseMethod7)
        #
        self.horizontalLayout_2.addWidget(self.GB_UseMethod)
        self.verticalLayout_2.addWidget(self.WG_Use)
        self.SA_Saved = QtWidgets.QScrollArea(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.SA_Saved.sizePolicy().hasHeightForWidth())
        self.SA_Saved.setSizePolicy(sizePolicy)
        self.SA_Saved.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.SA_Saved.setAutoFillBackground(False)
        self.SA_Saved.setFrameShape(QtWidgets.QFrame.Box)
        self.SA_Saved.setFrameShadow(QtWidgets.QFrame.Plain)
        self.SA_Saved.setLineWidth(1)
        self.SA_Saved.setMidLineWidth(0)
        self.SA_Saved.setWidgetResizable(True)
        self.SA_Saved.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
        self.SA_Saved.setObjectName("SA_Saved")
        self.WG_Saved = QtWidgets.QWidget()
        self.WG_Saved.setGeometry(QtCore.QRect(0, 0, 1172, 16))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.WG_Saved.sizePolicy().hasHeightForWidth())
        self.WG_Saved.setSizePolicy(sizePolicy)
        self.WG_Saved.setObjectName("WG_Saved")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.WG_Saved)
        self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.SA_Saved.setWidget(self.WG_Saved)
        self.verticalLayout_2.addWidget(self.SA_Saved)
        self.WG_Title.raise_()
        self.SA_Saved.raise_()
        self.WG_Use.raise_()

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)
    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.LB_Title.setText(_translate("Form", "NOITA存档备份2.0"))
        self.PB_AddSave.setText(_translate("Form", "新建并备份存档（等待）"))
        self.PB_OpenMain.setText(_translate("Form", "打开总存档目录"))
        self.GB_UseMethod.setTitle(_translate("Form", "使用方法"))
        self.LB_UseMethod1.setText(_translate("Form", "需要在游戏中关闭steam云存档"))
        self.LB_UseMethod2.setText(_translate("Form", "在需要保存的时候，点游戏的“保存与退出”来保存游戏"))
        self.LB_UseMethod3.setText(_translate("Form", "点击“备份存档”来对存档进行硬备份（写入备份存档或覆盖备份存档）"))
        self.LB_UseMethod4.setText(_translate("Form", "在需要回档的时候点游戏的“保存与退出”来退出游戏"))
        self.LB_UseMethod5.setText(_translate("Form", "点击“替换存档”来恢复存档（将备份存档替换正常存档）"))
        self.LB_UseMethod6.setText(_translate("Form", "点击“删除存档”来移除同级目录内的此脚本生成的硬备份"))
        self.LB_UseMethod7.setText(_translate("Form", "不要修改存档路径名，不要修改存档中的“SL_Messages.json”文件"))

# todo 主程序
class NoitaSLScript(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = MainWindow()
        self.ui.setupUi(self)
        self.setWindowTitle('Noita 存档备份程序 V2.0')
        self.initUi()
        self.show()
    def initUi(self):
        self.ui.PB_AddSave.clicked.connect(self.event_NewSaveFile) # 新建并备份存档
        self.ui.PB_OpenMain.clicked.connect(self.event_OpenPath) # 打开存档路径
        # 在这里获取实际文件的存档，记录在save_list里，最后重排列表
        self.get_real_savelist() # 获取实际存档
        self.resort_list() # 重排列表
        self.refresh_list() # 刷新存档列表界面
        self.write_sort_by_path() # 根据当前排序写入json文件

    # 获取实际存档
    def get_real_savelist(self):
        global save_list
        save_list = []
        # 在path_save00的上级路径中，搜索所有前缀为save00_SL_的目录，将其加入列表
        folder_list = os.listdir(path_program)
        for f in range(len(folder_list)):
            if folder_list[f].startswith('save00_SL_'):
                # PATH获取方法：
                message_json = path_program + '\\' + folder_list[f] + '\\SL_Messages.json'
                use_suffix = False
                # 检查如果存在json文件，则使用json文件中的PATH
                if os.path.exists(message_json):
                    use_delete = False
                    with open(message_json, 'r') as fi: # w+
                        try:
                            ele = json.loads(fi.read())
                            if ele['PATH'] != '_SL_Safety':
                                save_list.append(ele)
                        except:
                            use_delete = True
                    if use_delete:
                        os.remove(message_json)
                else:
                    use_suffix = True
                # 如果不存在json文件，则使用文件夹名的后缀
                if use_suffix:
                    path_name = folder_list[f].replace('save00', '')
                    if path_name != '_SL_Safety':
                        save_list.append({'NAME': '', 'SORT': str(f), 'PATH': path_name, 'NOTE': '', 'IMAGE': ''})
                    # 再新建一个json文件
                    with open(message_json, 'w') as fi:
                        json.dump({'NAME': '', 'SORT': str(f), 'PATH': path_name, 'NOTE': '', 'IMAGE': ''}, fi)
        pass

    # 打开存档路径
    def event_OpenPath(self): # 打开存档路径
        if os.path.exists(path_program):  # 存档路径是否存在
            startfile(path_program)  # 打开文件夹窗口

    # 随机八位字符生成器
    def random_str(self, randomlength=8):
        s = ''
        chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
        length = len(chars) - 1
        for i in range(randomlength):
            s += chars[random.randint(0, length)]
        return s

    # 随机图片
    def random_image(self):
        # 在path_images里获取随机一张图片路径
        image_list = os.listdir(path_images)
        image_path = path_images + '\\' + image_list[random.randint(0, len(image_list) - 1)]
        return image_path

    # 增加元素
    def add_element(self):
        # 新建的存档的排序设为-1
        self.random_image()
        ele = {'NAME': '', 'SORT': '-1', 'PATH': '_SL_' + self.random_str(), 'NOTE': '', 'IMAGE': self.random_image()}
        save_list.insert(0, ele)
        return ele

    # 刷新存档列表界面（当newElement不为空时，新建此元素）
    def refresh_list(self, newElementPath=''):
        self.clear_savelist_layout() # 清除列表显示
        for eleIndex in range(len(save_list)):
            ele = save_list[eleIndex]
            element_ui = WidgetElement()
            element_ui.set_display(ele)
            element_ui.PB_Clear.clicked.connect(self.after_delete_element)  ##
            element_ui.LE_Name.textChanged.connect(self.write_sort_by_path)  ##
            element_ui.PTE_Note.textChanged.connect(self.write_sort_by_path)  ##
            element_ui.PB_SortUp.clicked.connect(self.after_sort_changed)  ##
            element_ui.PB_SortDown.clicked.connect(self.after_sort_changed)  ##
            element_ui.PB_RandomImage.clicked.connect(self.after_sort_changed)  ##
            if eleIndex == 0: # 第一个
                element_ui.PB_SortUp.setEnabled(False)
                element_ui.PB_SortUp.setStyleSheet("background-color: rgb(80, 80, 80);")
            if eleIndex == len(save_list) - 1: # 最后一个
                element_ui.PB_SortDown.setEnabled(False)
                element_ui.PB_SortDown.setStyleSheet("background-color: rgb(80, 80, 80);")
            # 加载图片save_list[eleIndex]['IMAGE']到element_ui.LB_ImageDisplay上
            if ele['IMAGE'] != '':
                pixmap = QPixmap(ele['IMAGE'])
                proporation = pixmap.width() != 0 and pixmap.height() / pixmap.width() or 0
                pixmap.setDevicePixelRatio(proporation)
                w, h = element_ui.LB_ImageDisplay.width(), element_ui.LB_ImageDisplay.height()
                pixmap = pixmap.scaled(int(w / 4 * 3), int(h / 4 * 3))
                element_ui.LB_ImageDisplay.setPixmap(pixmap)
            else:
                element_ui.LB_ImageDisplay.setText('没有找到图片')
            #
            self.ui.WG_Saved.layout().addWidget(element_ui)
            if ele['PATH'] == newElementPath:  # 新建存档
                element_ui.event_FileSave()
                # 在存档文件夹里新建json文件
                with open(path_save00 + ele['PATH'] + '\\SL_Messages.json', 'w') as f:
                    json.dump(ele, f)

    # 清除列表显示
    def clear_savelist_layout(self):
        item_count = self.ui.WG_Saved.layout().count()
        for i in range(item_count):
            self.ui.WG_Saved.layout().removeItem(self.ui.WG_Saved.layout().itemAt(item_count - i - 1))

    # 重排列表（按从小到大排序），例如：1, -1, 4, 5 --> -1, 1, 4, 5
    def resort_list(self):
        save_list.sort(key=lambda x: x['SORT'])

    # 归一化列表序数（不检查序数，按数组顺序重置SORT序数），例如：-1, 1, 1, 2, 4, 6 --> 1, 2, 3, 4, 5, 6
    def down_list(self):
        for i in range(len(save_list)):
            save_list[i]['SORT'] = str(i)
        pass

    # 根据当前排序写入json文件
    def write_sort_by_path(self):
        sorts = []
        folder_list = os.listdir(path_program)
        for f in range(len(folder_list)):
            message_json = path_program + '\\' + folder_list[f] + '\\SL_Messages.json'
            if os.path.exists(message_json):
                json_read = json.load(open(message_json, 'r'))
                if json_read['PATH'] != '_SL_Safety':
                    sorts.append(json_read)
        for s in range(len(save_list)):
            # 在save_list中找到PATH一致的元素，将SORT修改
            for i in range(len(sorts)):
                if save_list[s]['PATH'] == sorts[i]['PATH']:
                    sorts[i]['SORT'] = save_list[s]['SORT']
                    sorts[i]['NAME'] = save_list[s]['NAME']
                    sorts[i]['NOTE'] = save_list[s]['NOTE']
                    sorts[i]['IMAGE'] = save_list[s]['IMAGE']
                    # 将sorts写入自身并覆盖
                    with open(path_program + '\\save00' + save_list[s]['PATH'] + '\\SL_Messages.json', 'w') as fi:
                        json.dump(sorts[i], fi)
                    break
            pass

    # 新建并备份存档
    def event_NewSaveFile(self):
        new_ele = self.add_element() # 增加元素
        self.resort_list() # 重排列表
        self.down_list() # 归一化列表序数
        self.refresh_list(new_ele['PATH']) # 刷新存档列表界面

    # 删除元素后，重排列表
    def after_delete_element(self):
        self.resort_list()  # 重排列表
        self.down_list()  # 归一化列表序数
        self.refresh_list()  # 刷新存档列表界面
        self.write_sort_by_path() # 根据当前排序写入json文件

    # 存档排序改变后
    def after_sort_changed(self):
        self.down_list() # 归一化列表序数
        self.resort_list() # 重排列表
        self.refresh_list()  # 刷新存档列表界面
        # print(save_list)
        self.write_sort_by_path() # 根据当前排序写入json文件


# todo 入口
if __name__ == '__main__':
    app = QApplication(sys.argv)
    artkit = NoitaSLScript()
    app.exec()
    pass